#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>

#define PACK_SIZE 1024
#define PACK_LEN  56
int send_num = 0;
int recv_num = 0;
char send_pack[PACK_SIZE];
char recv_pack[PACK_SIZE];

typedef  struct timeval timeval;
typedef  struct sockaddr_in sockaddr_in;
typedef  struct sockaddr sockaddr;
typedef  struct icmp icmp;
typedef  struct ip ip;

sockaddr_in from;

//计算一个rtt(往返时间)
double get_difftime(const struct timeval* begin,const struct timeval * end)
{
  return (double)( end->tv_sec - begin->tv_sec )*1000 + (double )(end->tv_usec - begin->tv_usec)/1000;
}

//信号处理函数
void handle(int arg)
{
  printf("--- %s statistics ---\n",);
}

//计算校验和
unsigned short chksum(unsigned short * addr,int len)
{
  int ret = 0;
  while(len > 1)
  {
    ret += *addr;
    addr++;//一个short占两个字节
    len -= 2;
  }

  //因为上面都是按照两个字节进行相加，存在len 位奇数的情况

  if(len == 1)
  {
    ret += *(char *)addr;
  }

  //因为这里是用32位整数来保存，需要将高16位在进行相加校验
  ret = (ret & 0xffff) + (ret >> 16);
  //高位和低位相加校验后还是有可能存在溢出
  ret = ret + (ret >> 16);
  return (unsigned short)~ret;
}

//************************进行发包********************************
int pack(int send_num,pid_t pid)
{
  //先将数据封装好
  icmp * p = (icmp *)send_pack; 
  p->icmp_type = ICMP_ECHO;//8
  p->icmp_code = 0;
  p->icmp_cksum = 0;//校验和初始为0
  p->icmp_seq = send_num;
  p->icmp_id = pid;
  timeval begin; 
  gettimeofday(&begin,NULL);
  memcpy((void *)p->icmp_data,(void *)&begin,sizeof(begin));
  p->icmp_cksum = chksum((unsigned short *)send_pack,PACK_LEN +8);
  return PACK_LEN + 8; 
}


void Send_pack(sockaddr_in peer,int fd,pid_t pid)
{
  send_num++;
  //进行分包
  int r = pack(send_num,pid);
  int ret = sendto(fd,send_pack,r,0,(sockaddr *)&peer,sizeof(peer));
  if(ret < 0)
  {
    printf("sendto failed\n");
  }
}

//************************进行收包********************************

void unpack(pid_t pid)
{
  ip * pip = (ip *)recv_pack;
  int count = pip->ip_hl;//ip 头部的头部长度标识
  int header_len = count * 4;
  icmp * picmp = (icmp *)(recv_pack + header_len);
  if(picmp->icmp_id != pid)
  {
    return;
  }
  timeval end ;
  gettimeofday(&end,NULL);
  double time = get_difftime((timeval *)&(picmp->icmp_data),&end);
  printf("%d bytes from %s: icmp_seq=%d ttl=%d time=%.2lf ms\n",PACK_LEN,inet_ntoa(from.sin_addr),picmp->icmp_seq,pip->ip_ttl,time);

}
void Recv_pack(int fd,pid_t pid)
{
  memset(recv_pack ,0x00,sizeof(recv_pack));
  socklen_t peer_len = sizeof(from);
  recvfrom(fd,recv_pack,sizeof(recv_pack),0,(sockaddr *)&from,&peer_len);
  recv_num ++;
  unpack(pid);
}

int main(int argc ,char * argv[])
{
  //struct timeval begin;
  //gettimeofday(&begin,NULL);
  //struct timeval end;
  //gettimeofday(&end,NULL);
  ////计算时间差，单位为ms
  //long sub = get_difftime(&begin,&end); 
  //printf("sub:%ld\n",sub);

  if(argc != 2)
  {
    printf("Usage: ./ping [ip/domin]\n");
    exit(1);
  }

  struct in_addr addr;
  struct hostent * pent = NULL;
  //有可能ping 的不是域名，而是ip地址
  if( (addr.s_addr = inet_addr(argv[1])) == INADDR_NONE)
  {//说明ping的是域名

    pent = gethostbyname(argv[1]);
    if(pent == NULL)
    {
      perror("gethostbyname");
      exit(2);
    }
    memcpy((char * )&addr,(char *)pent->h_addr,pent->h_length);
  }
  //打印出域名所对应的ip
 // printf("IPHostname：%s\n",pent->h_name);
 // printf("IPAddress:%s\n",inet_ntoa(*((struct in_addr*)pent->h_addr)));
 

  //创建一个套接字
  int fd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
  if(fd < 0)
  {
    perror("socket");
    exit(3);
  }
  
  //这里ip协议为网络层协议，不用建立连接
  sockaddr_in peer;
  peer.sin_family = AF_INET;
  peer.sin_addr = addr;

  //唯一标识符，进程id
  pid_t pid = getpid();

  signal(SIGINT,handle);
  printf("PING %s (%s) %d bytes of data\n",pent->h_name,inet_ntoa(addr),PACK_LEN);

  while(1)
  {
    Send_pack(peer,fd,pid);
    Recv_pack(fd,pid);
    sleep(1);
  }
 
  return 0;
}
